Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: format SharedDB operations as human-readable list of changes #2877

Merged
merged 9 commits into from
Apr 19, 2024

Conversation

jessicamcinchak
Copy link
Member

@jessicamcinchak jessicamcinchak commented Mar 11, 2024

Thinking about how we'll display recent operations in the "undo" dialog.

Our ShareDB operations are formatted according to Version 0 of the JSON OT type as described here https://github.com/ottypes/json0?tab=readme-ov-file#summary-of-operations

I was really hoping to find a library to handle pretty-printing & parsing for us, but no such luck so I've started writing our own translator. Lots of room for improvement here, but it's a baseline we can iterate from & keep thinking on for now?

To test please run window.featureFlags.toggle("UNDO") in your console first !

Screenshot from 2024-04-12 16-28-20

Todo:

  • recreate vultr with fresh secrets
  • unit tests for formatOps()
  • add feature flag so we can merge visual changes for easy testing until side panel tab is ready, but not yet display to all editors

@jessicamcinchak
Copy link
Member Author

Copy link

github-actions bot commented Mar 11, 2024

Removed vultr server and associated DNS entries

@jessicamcinchak jessicamcinchak requested a review from a team April 12, 2024 15:05
@jessicamcinchak jessicamcinchak marked this pull request as ready for review April 17, 2024 20:17
];

expect(formatOps(flowWithChecklist, ops)).toEqual([
'Updated Checklist text from "Which fruits?" to "Which vegetables?"',
Copy link
Member Author

@jessicamcinchak jessicamcinchak Apr 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like helpful context to print the value of the updated data prop here, but it's a big difference between showing how text or title change than it is, for example, help text.

We may want to consider making a sort of "black list" of data props where we'd only show the name - eg "Updated Checklist moreInfo"- without the values? Could also possibly address via UI design by nesting changes list within accordion drawers in side panel or similar?

I plan to leave in as-is initially for explicitness, but would expect this feedback to come up pretty early in testing !

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds sensible - a mapping of fields which do and don't get their values displayed makes sense.

});

return output;
};
Copy link
Member Author

@jessicamcinchak jessicamcinchak Apr 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future scope:

  • Live edits are tracked via operations, which this formatter "translates" into something human readable
  • Publishing changes are summarised via a JSON diff of two flow structures, which we summarise as alteredNodes - currently this may not perfectly match up to the summarised operations
  • For more consistency in the future between "edit history" & "publishing changes", our publishing JSON diff could first be mapped to a JSON OT using a package like this https://github.com/kbadk/json0-ot-diff - then both could share this tranlator

Don't think this is necesary just yet as recent changes to publishing are testing well, but just putting it on our radar !

@jessicamcinchak jessicamcinchak changed the title wip: log formatted operations feat: format SharedDB operations as human-readable list of changes Apr 17, 2024
Copy link
Contributor

@DafyddLlyr DafyddLlyr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good!

One issue with just the complexity of formatOps() - let me know what you think.

editor.planx.uk/src/@planx/graph/index.ts Outdated Show resolved Hide resolved
* Translates a list of ShareDB operations into a human-readable change summary.
* See https://github.com/ottypes/json0?tab=readme-ov-file#summary-of-operations
*/
export const formatOps = (graph: Graph, ops: any[]): string[] => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const formatOps = (graph: Graph, ops: any[]): string[] => {
export const formatOps = (graph: Graph, ops: OT.Op[]): string[] => {

Not totally sure on types here - is this right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - I got this mostly working - OT.Op is a big union type, so individual handleAdd() etc methods need to do a bit of casting like op as Op.Object.Add to match their semantic meaning. Look okay?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment below about type guards using is. I think under the hood it's essentially the same thing, but wanted to raise as an option.

editor.planx.uk/src/@planx/graph/index.ts Show resolved Hide resolved
];

expect(formatOps(flowWithChecklist, ops)).toEqual([
'Updated Checklist text from "Which fruits?" to "Which vegetables?"',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds sensible - a mapping of fields which do and don't get their values displayed makes sense.

}
const operationTypes = Object.keys(op);

if (operationTypes.includes("od") && operationTypes.includes("oi")) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: If we made this a type guard, we could avoid the casting here if we wanted to -

const isUpdate = (op: OT.Op): op is OT.Object.Replace => {
  const operationTypes = Object.keys(op);
  return operationTypes.includes("od") && operationTypes.includes("oi");
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is super useful to keep in mind & I'll definitley come back to it - will personally feel more confident full type-guarding after more testing of various operation scenarios !

@jessicamcinchak jessicamcinchak merged commit 8b84d6f into main Apr 19, 2024
12 checks passed
@jessicamcinchak jessicamcinchak deleted the jess/formatted-operations branch April 19, 2024 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants